<html><head><meta charset="utf-8"><title>实战：给 Webpack 项目添加 modern 模式打包-慕课专栏</title>
			<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
			<meta name="renderer" content="webkit">
			<meta property="qc:admins" content="77103107776157736375">
			<meta property="wb:webmaster" content="c4f857219bfae3cb">
			<meta http-equiv="Access-Control-Allow-Origin" content="*">
			<meta http-equiv="Cache-Control" content="no-transform ">
			<meta http-equiv="Cache-Control" content="no-siteapp">
			<link rel="apple-touch-icon" sizes="76x76" href="https://www.imooc.com/static/img/common/touch-icon-ipad.png">
			<link rel="apple-touch-icon" sizes="120x120" href="https://www.imooc.com/static/img/common/touch-icon-iphone-retina.png">
			<link rel="apple-touch-icon" sizes="152x152" href="https://www.imooc.com/static/img/common/touch-icon-ipad-retina.png">
			<link href="https://moco.imooc.com/captcha/style/captcha.min.css" rel="stylesheet">
			<link rel="stylesheet" href="https://www.imooc.com/static/moco/v1.0/dist/css/moco.min.css?t=201907021539" type="text/css">
			<link rel="stylesheet" href="https://www.imooc.com/static/lib/swiper/swiper-3.4.2.min.css?t=201907021539">
			<link rel="stylesheet" href="https://static.mukewang.com/static/css/??base.css,common/common-less.css?t=2.5,column/zhuanlanChapter-less.css?t=2.5,course/inc/course_tipoff-less.css?t=2.5?v=201907051055" type="text/css">
			<link charset="utf-8" rel="stylesheet" href="https://www.imooc.com/static/lib/ueditor/themes/imooc/css/ueditor.css?v=201907021539"><link rel="stylesheet" href="https://www.imooc.com/static/lib/baiduShare/api/css/share_style0_16.css?v=6aba13f0.css"></head>
			<body><div id="main">

<div class="container clearfix" id="top" style="display: block; width: 1134px;">
    
    <div class="center_con js-center_con l" style="width: 1134px;">
        <div class="article-con">
                            <!-- 买过的阅读 -->
                <div class="map">
                    <a href="/read" target="_blank"><i class="imv2-feather-o"></i></a>
                    <a href="/read/29" target="_blank">Webpack 从零入门到工程化实战</a>
                    <a href="" target="_blank">
                        <span>
                            / 5-6 实战：给 Webpack 项目添加 modern 模式打包
                        </span>
                    </a>
                </div>

            


            <div class="art-title" style="margin-top: 0px;">
                实战：给 Webpack 项目添加 modern 模式打包
            </div>
            <div class="art-info">
                
                <span>
                    更新时间：2019-07-18 18:39:54
                </span>
            </div>
            <div class="art-top">
                                <img src="https://img.mukewang.com/5cd964ea0001a95806400359.jpg" alt="">
                                                <div class="famous-word-box">
                    <img src="https://www.imooc.com/static/img/column/bg-l.png" alt="" class="bg1 bg">
                    <img src="https://www.imooc.com/static/img/column/bg-r.png" alt="" class="bg2 bg">
                    <div class="famous-word">学习这件事不在乎有没有人教你，最重要的是在于你自己有没有觉悟和恒心。 <p class="author">—— 法布尔</p></div>
                </div>
                            </div>
            <div class="art-content js-lookimg">
                <div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在 Vue-CLI 3 中有个<a href="https://cli.vuejs.org/zh/guide/browser-compatibility.html#%E7%8E%B0%E4%BB%A3%E6%A8%A1%E5%BC%8F">Modern Mode 的新功能</a>，在现代模式（modern mode）下，打包出来的 JavaScript 代码是 ES2015+的，官方的解释是这样的：</p>
</div><div class="cl-preview-section"><blockquote>
<p style="font-size: 20px; line-height: 38px;">有了 Babel 我们可以兼顾所有最新的 ES2015+ 语言特性，但也意味着我们需要交付转译和 polyfill 后的包以支持旧浏览器。这些转译后的包通常都比原生的 ES2015+ 代码会更冗长，运行更慢。现如今绝大多数现代浏览器都已经支持了原生的 ES2015，所以因为要支持更老的浏览器而为它们交付笨重的代码是一种浪费。</p>
</blockquote>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在支持 ES2015+（ES6+）的浏览器中，我们在 JavaScript 编写的绝大多数 ES 语法都被浏览器原生支持：</p>
</div><div class="cl-preview-section"><ul>
<li style="font-size: 20px; line-height: 38px;"><code>Class</code> 语法；</li>
<li style="font-size: 20px; line-height: 38px;"><code>async</code>/<code>await</code> 语法；</li>
<li style="font-size: 20px; line-height: 38px;">箭头函数、模板等新语法；</li>
<li style="font-size: 20px; line-height: 38px;">支持新的 API：<code>Promise</code>、<code>Fetch</code>、<code>Map</code>、<code>Set</code>等。</li>
</ul>
</div><div class="cl-preview-section"><blockquote>
<p style="font-size: 20px; line-height: 38px;">Tips：ES2015 是 2015 年发布的第六个版本的 ECMAScript 标准，所以 ES2015 和 ES6 是一回事，本文延续 Vue-CLI 文档的称呼。</p>
</blockquote>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">直接原生的支持 ES2015+语法的好处是：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">不需要额外的 polyfill 就可以支持最新的 ES6 语法，减少了代码体积；</li>
<li style="font-size: 20px; line-height: 38px;">体积小了，那么下载速度就加快；</li>
<li style="font-size: 20px; line-height: 38px;">JavaScript 是解释执行的，所以更小的代码体积和更现代的代码，能够提升代码的解析速度（parse），运行也更快。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">「对于 Vue 的 Hello World 应用（<code>vue create hello-world</code>）来说，现代版的包已经小了 16%。在生产环境下，现代版的包通常都会表现出显著的解析速度和运算速度，从而改善应用的加载性能」。由此可见，相对于 Hello World 这样的 Demo 应用都有这么大的体积减少和性能提升，那么对于我们复杂的项目来说使用现代模式的吸引力就更大了。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">看到这些好处，我们是不是也想在自己的项目中尝试下现代模式呢？如果我们的项目不是 Vue-CLI 3 作为打包工具，那么我们可以通过本文的实战来给自己的项目添加现代模式。已经使用 Vue-CLI 3 的项目，也可以通过本文学习到现代模式的原理。</p>
</div><div class="cl-preview-section"><h2 id="modern-mode-实现原理" style="font-size: 30px;">Modern Mode 实现原理</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">Modern Mode 代码通过 Babel 是很容易编译出来的，Modern Mode 实现难点是如何做好浏览器的兼容性，即在不支持 ES2015+ 在浏览器中能够正常执行 JavaScript 代码。为了实现兼容性，Webpack 需要在打包的时候将原始的 JavaScript 代码打包出两份 JavaScript 代码，一份用于老版本的浏览器，一份用于现代浏览器。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在浏览器中，应该根据所处的 JavaScript 语法特性进行选择，在浏览器环境中进行 ES 语法特性检测没有特比好的解决方案。最终我们可以通过检测<code>script</code>标签的<code>type="module"</code>的方式进行现代浏览器和老版浏览器的适配。这种方式是参考了 Phillip Walton 的这篇文章 <a href="https://philipwalton.com/articles/deploying-es2015-code-in-production-today/">Deploying ES2015+ Code in Production Today</a>（<a href="https://jdc.jd.com/archives/4911">中文版本</a>），其中提出了基于 script 标签的 <code>type="module"</code> 和 <code>nomodule</code> 属性 区分出当前浏览器对 ES2015+ 的支持程度。具体原理实现可以参考下面的代码：</p>
</div><div class="cl-preview-section"><pre class=" language-html"><code class="prism  language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>module<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>main.js<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token script language-javascript"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">nomodule</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>main.legacy.js<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span><span class="token script language-javascript"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在上面的代码中，如果浏览器支持 ES6 的<code>module</code>语法，那么就可以执行<code>main.js</code>的代码，从而忽略下面的<code>nomodule</code>代码，这样现代浏览器就可以通过这种方式执行我们的 ES2015+ 语法的 JavaScript 文件。而对于不支持<code>module</code>语法的浏览器，那么<code>type="module"</code>不被识别，而会执行后面的<code>main.legacy.js</code>代码。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">这就是我们要实现 Modern Mode 的基本原理。在<a href="https://caniuse.com/#search=module">caniuse.com</a>网站上我们可以看到<code>&lt;script type="module"&gt;</code>的支持情况：<br>
<code>China 61.65% + 0.39% = 62.04%</code>。这个数据来看，在国内已经有 60%+的浏览器支持 ES2015+语法了，我们可以让着 60%+的用户使用更快更好的 JavaScript 加载、解析和执行体验，并且我们不需要修改任何代码，就可以让 Webpack 来做这个优化，今后随着浏览器升级，我们甚至可以全部使用 ES2015+语法。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img src="http://img.mukewang.com/5d2c22400001f33012610388.png" alt="图片描述" data-original="http://img.mukewang.com/5d2c22400001f33012610388.png" class="" style="cursor: pointer;"><br>
在使用<code>&lt;script type="module"&gt;</code>之前，我们还需要修复 Safari 10.1 和 iOS Safari 10.3 已经支持<code>module</code>语法，但是不支持 script 标签<code>nomodule</code>属性的一个问题，具体的代码来自<a href="https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc">A polyfill is available for Safari 10.1/iOS Safari 10.3</a>。</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">var</span> check <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'script'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span><span class="token string">'noModule'</span> <span class="token keyword">in</span> check<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token string">'onbeforeload'</span> <span class="token keyword">in</span> check<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">var</span> support <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
        document<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span>
            <span class="token string">'beforeload'</span><span class="token punctuation">,</span>
            <span class="token keyword">function</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>target <span class="token operator">===</span> check<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                    support <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>e<span class="token punctuation">.</span>target<span class="token punctuation">.</span><span class="token function">hasAttribute</span><span class="token punctuation">(</span><span class="token string">'nomodule'</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token operator">!</span>support<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                    <span class="token keyword">return</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span>
                e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span>
            <span class="token boolean">true</span>
        <span class="token punctuation">)</span><span class="token punctuation">;</span>

        check<span class="token punctuation">.</span>type <span class="token operator">=</span> <span class="token string">'module'</span><span class="token punctuation">;</span>
        check<span class="token punctuation">.</span>src <span class="token operator">=</span> <span class="token string">'.'</span><span class="token punctuation">;</span>
        document<span class="token punctuation">.</span>head<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>check<span class="token punctuation">)</span><span class="token punctuation">;</span>
        check<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><h3 id="vue-cli-3-modern-mode-实现分析">Vue-CLI 3 Modern Mode 实现分析</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">上面的原理介绍完了，我们来看下具体 Vue-CLI 3 在 Modern Mode 模式下的产出：</p>
</div><div class="cl-preview-section"><pre class=" language-html"><code class="prism  language-html"><span class="token comment">&lt;!--预取--&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>link</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span>/css/app.e2713bb0.css</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation">=</span>preload</span> <span class="token attr-name">as</span><span class="token attr-value"><span class="token punctuation">=</span>style</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>link</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span>/js/app.962c146a.js</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation">=</span>modulepreload</span> <span class="token attr-name">as</span><span class="token attr-value"><span class="token punctuation">=</span>script</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>link</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span>/js/chunk-vendors.ab5b1059.js</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation">=</span>modulepreload</span> <span class="token attr-name">as</span><span class="token attr-value"><span class="token punctuation">=</span>script</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>link</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span>/css/app.e2713bb0.css</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation">=</span>stylesheet</span><span class="token punctuation">&gt;</span></span>

<span class="token comment">&lt;!--type=module--&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span>module</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span>/js/chunk-vendors.ab5b1059.js</span><span class="token punctuation">&gt;</span></span><span class="token script language-javascript"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span>module</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span>/js/app.962c146a.js</span><span class="token punctuation">&gt;</span></span><span class="token script language-javascript"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">&gt;</span></span><span class="token script language-javascript">
<span class="token comment">// 兼容 Safari 10</span>
<span class="token operator">!</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">var</span> e <span class="token operator">=</span> document<span class="token punctuation">,</span>
    t <span class="token operator">=</span> e<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'script'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span><span class="token string">'noModule'</span> <span class="token keyword">in</span> t<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token string">'onbeforeload'</span> <span class="token keyword">in</span> t<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">var</span> n <span class="token operator">=</span> <span class="token operator">!</span><span class="token number">1</span><span class="token punctuation">;</span>
    e<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span>
      <span class="token string">'beforeload'</span><span class="token punctuation">,</span>
      <span class="token keyword">function</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>target <span class="token operator">===</span> t<span class="token punctuation">)</span> n <span class="token operator">=</span> <span class="token operator">!</span><span class="token number">0</span><span class="token punctuation">;</span>
        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>e<span class="token punctuation">.</span>target<span class="token punctuation">.</span><span class="token function">hasAttribute</span><span class="token punctuation">(</span><span class="token string">'nomodule'</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token operator">!</span>n<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
        e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token operator">!</span><span class="token number">0</span>
    <span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token punctuation">(</span>t<span class="token punctuation">.</span>type <span class="token operator">=</span> <span class="token string">'module'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token punctuation">(</span>t<span class="token punctuation">.</span>src <span class="token operator">=</span> <span class="token string">'.'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      e<span class="token punctuation">.</span>head<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span><span class="token punctuation">,</span>
      t<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
<span class="token comment">&lt;!--老浏览器代码--&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span>/js/chunk-vendors-legacy.ecd76ec1.js</span> <span class="token attr-name">nomodule</span><span class="token punctuation">&gt;</span></span><span class="token script language-javascript"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span>/js/app-legacy.7c8e48ce.js</span> <span class="token attr-name">nomodule</span><span class="token punctuation">&gt;</span></span><span class="token script language-javascript"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在实战部分实现<code>prefetch</code>插件的时候，已经介绍过<code>link</code>标签的<code>prefetch</code>和<code>preload</code>，在 Vue 的产出中，使用了<code>preload</code>，并且配合了<code>as</code>和<code>modulepreload</code>。使用<code>as</code>属性，可以明确的告诉浏览器预加载的资源类型，从而使浏览器能够更加精确的去优化加载资源。Chrome 从 64 版本后 开始 「实验性的支持这个特征<code>modulepreload</code>」，这个属性值，<code>&lt;link rel="modulepreload"&gt;</code>是 <code>&lt;link rel="preload"&gt;</code> 的特定模块（module）版本，可以针对 ES Modules 进行特定的优化和处理。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">最后在产出物的最后在使用<code>&lt;script type="module"&gt;</code>加载现代浏览器执行的 JavaScript 代码，使用<code>&lt;script nomodule&gt;</code>加载不支持 ES6 语法的 polyfill 代码。从上面的产出来看，我们要实现 Modern Mode 模式代码，需要做的事情是：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">让 Webpack 打包两份代码，一份是支持现代浏览器的代码，一份是不支持 ES6 语法的<code>legacy</code>代码；</li>
<li style="font-size: 20px; line-height: 38px;">处理 HTML 中对代码的引入，增加<code>modulepreload</code>、添加第一步打出的两份 bundle 地址，并且插入对应的<code>script</code>属性和 Safari 10 的 polyfill 代码。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在第一步打出两份代码的方式，Vue-CLI 3 是执行两次打包，两次打包通过不同的 Babel 配置产生不同的代码，具体来说，在 Vue-CLI 3 的<code>babel-preset-app</code>中，设置<code>@babel/preset-env</code>的<code>targets={esmodule:true}</code>，不转换所有的语法也不添加 polyfill，生成 ES6 的能被现代浏览器执行的代码：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/babel-preset-app/index.js#L89</span>
<span class="token keyword">let</span> targets<span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span>VUE_CLI_BABEL_TARGET_NODE<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// running tests in Node.js</span>
    targets <span class="token operator">=</span> <span class="token punctuation">{</span>node<span class="token punctuation">:</span> <span class="token string">'current'</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span>VUE_CLI_BUILD_TARGET <span class="token operator">===</span> <span class="token string">'wc'</span> <span class="token operator">||</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span>VUE_CLI_BUILD_TARGET <span class="token operator">===</span> <span class="token string">'wc-async'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// targeting browsers that at least support ES2015 classes</span>
    <span class="token comment">// https://github.com/babel/babel/blob/master/packages/babel-preset-env/data/plugins.json#L52-L61</span>
    targets <span class="token operator">=</span> <span class="token punctuation">{</span>
        browsers<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'Chrome &gt;= 49'</span><span class="token punctuation">,</span> <span class="token string">'Firefox &gt;= 45'</span><span class="token punctuation">,</span> <span class="token string">'Safari &gt;= 10'</span><span class="token punctuation">,</span> <span class="token string">'Edge &gt;= 13'</span><span class="token punctuation">,</span> <span class="token string">'iOS &gt;= 10'</span><span class="token punctuation">,</span> <span class="token string">'Electron &gt;= 0.36'</span><span class="token punctuation">]</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>process<span class="token punctuation">.</span>env<span class="token punctuation">.</span>VUE_CLI_MODERN_BUILD<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// targeting browsers that support &lt;script type="module"&gt;</span>
    targets <span class="token operator">=</span> <span class="token punctuation">{</span>esmodules<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
    targets <span class="token operator">=</span> rawTargets<span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">//...</span>
<span class="token keyword">const</span> envOptions <span class="token operator">=</span> <span class="token punctuation">{</span>
    corejs<span class="token punctuation">:</span> <span class="token number">3</span><span class="token punctuation">,</span>
    spec<span class="token punctuation">,</span>
    loose<span class="token punctuation">,</span>
    debug<span class="token punctuation">,</span>
    modules<span class="token punctuation">,</span>
    targets<span class="token punctuation">,</span>
    useBuiltIns<span class="token punctuation">,</span>
    ignoreBrowserslistConfig<span class="token punctuation">,</span>
    configPath<span class="token punctuation">,</span>
    include<span class="token punctuation">,</span>
    exclude<span class="token punctuation">:</span> polyfills<span class="token punctuation">.</span><span class="token function">concat</span><span class="token punctuation">(</span>exclude <span class="token operator">||</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    shippedProposals<span class="token punctuation">,</span>
    forceAllTransforms
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token comment">// ...</span>
<span class="token comment">// pass options along to babel-preset-env</span>
presets<span class="token punctuation">.</span><span class="token function">unshift</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'@babel/preset-env'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> envOptions<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">处理 HTML 代码，添加 Modern Mode 代码支持则是在<code>cli-service</code>中的<a href="https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-service/lib/webpack/ModernModePlugin.js"><code>lib/webpack/ModernModePlugin.js</code></a>通过编写一个<code>html-webpack-plugin</code>的插件而实现的，这个跟我们之前[TODO 插件实战文章链接]实战编写 Webpack 插件一样的「套路」。</p>
</div><div class="cl-preview-section"><h2 id="如何打包出来-modern-mode-的-javascript-代码" style="font-size: 30px;">如何打包出来 Modern Mode 的 JavaScript 代码</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">通过上面的介绍，我们知道了：要实现 Webpack 打包出支持 Modern Mode 的代码，需要设置<code>@babel/preset-env</code>的<code>targets={esmodule: true}</code>，而要打包出两份代码，则需要 Webpack 打包两次。那么在我们实际项目中，可以通过我们首先可以使用 Webpack 的 API 来打包，并且将 API 改写成 Promise 的方式，具体代码示例如下：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> webpack <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'webpack'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> webpackConfig <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./webpack.config.js'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token function-variable function">webpackPromise</span> <span class="token operator">=</span> webpackConfig <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span>resolve<span class="token punctuation">,</span> reject<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token function">webpack</span><span class="token punctuation">(</span>webpackConfig<span class="token punctuation">,</span> <span class="token punctuation">(</span>err<span class="token punctuation">,</span> stats<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token function">reject</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token keyword">return</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>stats<span class="token punctuation">.</span><span class="token function">hasErrors</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token keyword">const</span> info <span class="token operator">=</span> stats<span class="token punctuation">.</span><span class="token function">toJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>info<span class="token punctuation">.</span>errors<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token function">reject</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token keyword">return</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
            <span class="token function">resolve</span><span class="token punctuation">(</span>stats<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token comment">// 下面是使用</span>
<span class="token function">webpackPromise</span><span class="token punctuation">(</span>webpackConfig<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>stats <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>stats<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token keyword">catch</span><span class="token punctuation">(</span>e <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">上面的<code>webpackPromise</code>我们已经将 Webpack 的 API 调用改成了 Promise 的方式，那么下面是我们怎么将非现代浏览器的 Webpack 配置修改成现代浏览器的，即设置<code>@babel/preset-env</code>的<code>targets={esmodule: true}</code>，这里我们有很多办法，下面我推荐两种方式：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">使用<a href="https://github.com/survivejs/webpack-merge">webpack-merge</a>；</li>
<li style="font-size: 20px; line-height: 38px;">零件配置方式。</li>
</ol>
</div><div class="cl-preview-section"><h3 id="webpack-merge">Webpack-merge</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">webpack-merge 是我们常用的将 Webpack 的 Object 类型配置进行合并（merge）的方法，在之前《Webpack 环境相关配置及配置文件拆分[TODO 链接]》中提到过 webpack-merge 的使用，可以将多个配置文件的配置进行合并，我们在这里也是使用这个插件的，具体代码示例如下：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// 首先我们假设下面是 webpack.config.js 内容</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
    mode<span class="token punctuation">:</span> <span class="token string">'production'</span><span class="token punctuation">,</span>
    entry<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        main<span class="token punctuation">:</span> <span class="token string">'./src/index.js'</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    output<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        filename<span class="token punctuation">:</span> <span class="token string">'[name]-legacy-[chunkhash].js'</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    module<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        rules<span class="token punctuation">:</span> <span class="token punctuation">[</span>
            <span class="token punctuation">{</span>
                test<span class="token punctuation">:</span> <span class="token regex">/\.js$/</span><span class="token punctuation">,</span>
                loader<span class="token punctuation">:</span> <span class="token string">'babel-loader'</span><span class="token punctuation">,</span>
                options<span class="token punctuation">:</span> <span class="token punctuation">{</span>
                    presets<span class="token punctuation">:</span> <span class="token punctuation">[</span>
                        <span class="token punctuation">[</span>
                            <span class="token string">'@babel/preset-env'</span><span class="token punctuation">,</span>
                            <span class="token punctuation">{</span>
                                useBuiltIns<span class="token punctuation">:</span> <span class="token string">'usage'</span><span class="token punctuation">,</span>
                                corejs<span class="token punctuation">:</span> <span class="token number">3</span>
                            <span class="token punctuation">}</span>
                        <span class="token punctuation">]</span>
                    <span class="token punctuation">]</span><span class="token punctuation">,</span>
                    plugins<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'babel-plugin-transform-dynamic-import-default'</span><span class="token punctuation">]</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">]</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token comment">// 那么我们在打包脚本中可以使用 merge 来替换 preset 的配置</span>
<span class="token keyword">const</span> merge <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'webpack-merge'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> webpackConfig <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./webpack.config'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// 这里使用 merge.smart 方法</span>
<span class="token keyword">const</span> modernConfig <span class="token operator">=</span> merge<span class="token punctuation">.</span><span class="token function">smart</span><span class="token punctuation">(</span>webpackConfig<span class="token punctuation">,</span> <span class="token punctuation">{</span>
    output<span class="token punctuation">:</span> <span class="token punctuation">{</span>filename<span class="token punctuation">:</span> <span class="token string">'[name]-modern-[chunkhash].js'</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
    module<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        rules<span class="token punctuation">:</span> <span class="token punctuation">[</span>
            <span class="token punctuation">{</span>
                test<span class="token punctuation">:</span> <span class="token regex">/\.js$/</span><span class="token punctuation">,</span>
                loader<span class="token punctuation">:</span> <span class="token string">'babel-loader'</span><span class="token punctuation">,</span>
                options<span class="token punctuation">:</span> <span class="token punctuation">{</span>
                    presets<span class="token punctuation">:</span> <span class="token punctuation">[</span>
                        <span class="token punctuation">[</span>
                            <span class="token string">'@babel/preset-env'</span><span class="token punctuation">,</span>
                            <span class="token punctuation">{</span>
                                targets<span class="token punctuation">:</span> <span class="token punctuation">{</span>esmodules<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span>
                            <span class="token punctuation">}</span>
                        <span class="token punctuation">]</span>
                    <span class="token punctuation">]</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">]</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 下面是合并后的 webpack config</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>modernConfig<span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><h3 id="零件配置方式">零件配置方式</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">如果项目的 webpack 配置文件按照在《Webpack 环境相关配置及配置文件拆分[TODO webpack ]》文章中方法，将 loader 相关的配置拆成了函数，例如我们项目中的 loader 配置是如下拆分的：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img src="http://img.mukewang.com/5d2c22760001ddc801250282.png" alt="图片描述" data-original="http://img.mukewang.com/5d2c22760001ddc801250282.png" class="" style="cursor: pointer;"><br>
这样每个 loader 配置都是一个类似下面的函数，通过传入的参数最终生成对应 loader 的配置：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// babel.js</span>
<span class="token comment">// 通过传入参数获取 babel loader 的配置</span>
module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> options <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> plugins <span class="token operator">=</span> <span class="token punctuation">(</span>options <span class="token operator">&amp;&amp;</span> options<span class="token punctuation">.</span>plugins<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
    <span class="token keyword">let</span> targets <span class="token operator">=</span> options<span class="token punctuation">.</span>browserslist<span class="token punctuation">;</span>
    <span class="token comment">// 是 modern 模式，但不是 modern 打包，那么 js 加上 legacy</span>
    <span class="token keyword">const</span> isModernBundle <span class="token operator">=</span> options<span class="token punctuation">.</span>modernBuild<span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>isModernBundle<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 这个是 modern 打包</span>
        targets <span class="token operator">=</span> <span class="token punctuation">{</span>esmodules<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> <span class="token punctuation">{</span>
        name<span class="token punctuation">:</span> <span class="token string">'babel-loader'</span><span class="token punctuation">,</span>
        loader<span class="token punctuation">:</span> require<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token string">'babel-loader'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        options<span class="token punctuation">:</span> <span class="token punctuation">{</span>
            cacheDirectory<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
            presets<span class="token punctuation">:</span> <span class="token punctuation">[</span>
                <span class="token punctuation">[</span>
                    <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'@babel/preset-env'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                    <span class="token punctuation">{</span>
                        debug<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
                        useBuiltIns<span class="token punctuation">:</span> <span class="token string">'usage'</span><span class="token punctuation">,</span>
                        corejs<span class="token punctuation">:</span> <span class="token number">3</span><span class="token punctuation">,</span>
                        targets<span class="token punctuation">,</span>
                        modules<span class="token punctuation">:</span> <span class="token boolean">false</span>
                    <span class="token punctuation">}</span>
                <span class="token punctuation">]</span>
            <span class="token punctuation">]</span><span class="token punctuation">,</span>
            plugins<span class="token punctuation">:</span> <span class="token punctuation">[</span>
                <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'@babel/plugin-syntax-dynamic-import'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'@babel/plugin-syntax-import-meta'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'@babel/plugin-proposal-class-properties'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'@babel/plugin-transform-new-target'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'@babel/plugin-transform-modules-commonjs'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                <span class="token punctuation">[</span>
                    <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'@babel/plugin-transform-runtime'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
                    <span class="token punctuation">{</span>
                        <span class="token comment">// corejs: false, // 默认值，可以不写</span>
                        regenerator<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token comment">// 通过 preset-env 已经使用了全局的 regeneratorRuntime, 不再需要 transform-runtime 提供的 不污染全局的 regeneratorRuntime</span>
                        helpers<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// 默认，可以不写</span>
                        useESModules<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token comment">// 不使用 es modules helpers, 减少 commonJS 语法代码</span>
                        absoluteRuntime<span class="token punctuation">:</span> path<span class="token punctuation">.</span><span class="token function">dirname</span><span class="token punctuation">(</span>require<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token string">'@babel/runtime/package.json'</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
                    <span class="token punctuation">}</span>
                <span class="token punctuation">]</span><span class="token punctuation">,</span>
                <span class="token operator">...</span>plugins
            <span class="token punctuation">]</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">上面的代码中，那么我们可以通过传入参数的方式得到现代浏览器的 Babel 配置：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// webpack config 类文件中</span>
<span class="token keyword">const</span> babelOptions <span class="token operator">=</span> <span class="token function">getBabelLoader</span><span class="token punctuation">(</span><span class="token punctuation">{</span>modernBuild<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
    module<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        rules<span class="token punctuation">:</span> <span class="token punctuation">[</span>
            <span class="token punctuation">{</span>
                test<span class="token punctuation">:</span> <span class="token regex">/\.js$/</span><span class="token punctuation">,</span>
                <span class="token operator">...</span>babelOptions
            <span class="token punctuation">}</span>
        <span class="token punctuation">]</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><h3 id="测试-modern-mode-打包效果">测试 modern mode 打包效果</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">通过上面的配置，我们可以完成 modern mode 模式打包了，下面编写个测试项目：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// src/index.js</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>dep1<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./dep-1.js'</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>dep2<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./dep-2.js'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> main <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Dependency 1 value:'</span><span class="token punctuation">,</span> dep1<span class="token punctuation">)</span><span class="token punctuation">;</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Dependency 2 value:'</span><span class="token punctuation">,</span> dep2<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> <span class="token punctuation">{</span>import1<span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">import</span><span class="token punctuation">(</span>
        <span class="token comment">/* webpackChunkName: "import1" */</span>
        <span class="token string">'./import-1.js'</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Dynamic Import 1 value:'</span><span class="token punctuation">,</span> import1<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">const</span> <span class="token punctuation">{</span>import2<span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">import</span><span class="token punctuation">(</span>
        <span class="token comment">/* webpackChunkName: "import2" */</span>
        <span class="token string">'./import-2.js'</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Dynamic Import 2 value:'</span><span class="token punctuation">,</span> import2<span class="token punctuation">)</span><span class="token punctuation">;</span>

    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Fetching data, awaiting response...'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> response <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">'http://jsonplaceholder.typicode.com/users'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">const</span> json <span class="token operator">=</span> <span class="token keyword">await</span> response<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Response:'</span><span class="token punctuation">,</span> json<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// src/dep-1.js</span>
<span class="token keyword">export</span> <span class="token keyword">const</span> dep1 <span class="token operator">=</span> <span class="token string">'dep-1'</span><span class="token punctuation">;</span>

<span class="token comment">// src/dep-2.js</span>
<span class="token keyword">export</span> <span class="token keyword">const</span> dep2 <span class="token operator">=</span> <span class="token string">'dep-2'</span><span class="token punctuation">;</span>

<span class="token comment">// src/import-1.js</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>dep1<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./dep-1'</span><span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">const</span> import1 <span class="token operator">=</span> <span class="token template-string"><span class="token string">`imported: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>dep1<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">;</span>

<span class="token comment">// src/import-2.js</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>dep2<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./dep-2'</span><span class="token punctuation">;</span>
<span class="token keyword">export</span> <span class="token keyword">const</span> import2 <span class="token operator">=</span> <span class="token template-string"><span class="token string">`imported: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>dep2<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">对应<code>webpack.config.js</code>的配置如下：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js">module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
    mode<span class="token punctuation">:</span> <span class="token string">'production'</span><span class="token punctuation">,</span>
    entry<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        main<span class="token punctuation">:</span> <span class="token string">'./src/index.js'</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    output<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        filename<span class="token punctuation">:</span> <span class="token string">'[name]-legacy-[chunkhash].js'</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    module<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        rules<span class="token punctuation">:</span> <span class="token punctuation">[</span>
            <span class="token punctuation">{</span>
                test<span class="token punctuation">:</span> <span class="token regex">/\.js$/</span><span class="token punctuation">,</span>
                loader<span class="token punctuation">:</span> <span class="token string">'babel-loader'</span><span class="token punctuation">,</span>
                options<span class="token punctuation">:</span> <span class="token punctuation">{</span>
                    presets<span class="token punctuation">:</span> <span class="token punctuation">[</span>
                        <span class="token punctuation">[</span>
                            <span class="token string">'@babel/preset-env'</span><span class="token punctuation">,</span>
                            <span class="token punctuation">{</span>
                                useBuiltIns<span class="token punctuation">:</span> <span class="token string">'usage'</span><span class="token punctuation">,</span>
                                corejs<span class="token punctuation">:</span> <span class="token number">3</span>
                            <span class="token punctuation">}</span>
                        <span class="token punctuation">]</span>
                    <span class="token punctuation">]</span><span class="token punctuation">,</span>
                    plugins<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'babel-plugin-transform-dynamic-import-default'</span><span class="token punctuation">]</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">]</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">打包脚本<code>build.js</code>内容如下：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// build.js</span>
<span class="token keyword">const</span> webpack <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'webpack'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> merge <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'webpack-merge'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> webpackConfig <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./webpack.config'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> <span class="token function-variable function">webpackPromise</span> <span class="token operator">=</span> webpackConfig <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span>resolve<span class="token punctuation">,</span> reject<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token function">webpack</span><span class="token punctuation">(</span>webpackConfig<span class="token punctuation">,</span> <span class="token punctuation">(</span>err<span class="token punctuation">,</span> stats<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token function">reject</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token keyword">return</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>stats<span class="token punctuation">.</span><span class="token function">hasErrors</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token keyword">const</span> info <span class="token operator">=</span> stats<span class="token punctuation">.</span><span class="token function">toJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>info<span class="token punctuation">.</span>errors<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token function">reject</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token keyword">return</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
            <span class="token function">resolve</span><span class="token punctuation">(</span>stats<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> modernConfig <span class="token operator">=</span> merge<span class="token punctuation">.</span><span class="token function">smart</span><span class="token punctuation">(</span>webpackConfig<span class="token punctuation">,</span> <span class="token punctuation">{</span>
    output<span class="token punctuation">:</span> <span class="token punctuation">{</span>filename<span class="token punctuation">:</span> <span class="token string">'[name]-modern-[chunkhash].js'</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
    module<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        rules<span class="token punctuation">:</span> <span class="token punctuation">[</span>
            <span class="token punctuation">{</span>
                test<span class="token punctuation">:</span> <span class="token regex">/\.js$/</span><span class="token punctuation">,</span>
                loader<span class="token punctuation">:</span> <span class="token string">'babel-loader'</span><span class="token punctuation">,</span>
                options<span class="token punctuation">:</span> <span class="token punctuation">{</span>
                    presets<span class="token punctuation">:</span> <span class="token punctuation">[</span>
                        <span class="token punctuation">[</span>
                            <span class="token string">'@babel/preset-env'</span><span class="token punctuation">,</span>
                            <span class="token punctuation">{</span>
                                targets<span class="token punctuation">:</span> <span class="token punctuation">{</span>esmodules<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span>
                            <span class="token punctuation">}</span>
                        <span class="token punctuation">]</span>
                    <span class="token punctuation">]</span><span class="token punctuation">,</span>
                    plugins<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'babel-plugin-transform-dynamic-import-default'</span><span class="token punctuation">]</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">]</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token function">webpackPromise</span><span class="token punctuation">(</span>webpackConfig<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">webpackPromise</span><span class="token punctuation">(</span>modernConfig<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">[</span>legacyStats<span class="token punctuation">,</span> modernStats<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token comment">// 输出老版本打包</span>
        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>legacyStats<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">{</span>chunks<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> modules<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> colors<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token comment">// 输出 modern 版本打包</span>
        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>modernStats<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">{</span>chunks<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> modules<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> colors<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token keyword">catch</span><span class="token punctuation">(</span>e <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">最终执行<code>node build.js</code>，结果如下：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img src="http://www.imooc.com/read/29/article/img/modern-log.png" alt="" data-original="http://www.imooc.com/read/29/article/img/modern-log.png" class="" style="cursor: pointer;"></p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">上面的 log 显示，我们的老版本浏览器的文件<code>main-legacy-f2fb0978e775b1b7d7e9.js</code>为 53K，而 Modern Mode 打包出来的文件<code>main-modern-4cfa0263ac7971396ede.js</code>才 3K！</p>
</div><div class="cl-preview-section"><h2 id="如何通过script标签处理-modern-mode-代码兼容性" style="font-size: 30px;">如何通过<code>script</code>标签处理 Modern Mode 代码兼容性</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">打包出来 ES2015+（modern） 和 ES2015-（legacy） 两个 bundle 文件还不是结束，还需要通过 html-webpack-plugin 插件将这两个 bundle 文件按照之前的介绍使用 <code>script</code> 标签的 <code>type="module"</code> 和 <code>nomodule</code> 添加到 HTML 中，最终得到如下的 HTML：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token operator">&lt;</span>script type<span class="token operator">=</span><span class="token string">"module"</span> src<span class="token operator">=</span><span class="token string">"main-modern-4cfa0263ac7971396ede.js"</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span>script<span class="token operator">&gt;</span>
<span class="token comment">// 下面是修复 safari 10 的 polyfill</span>
<span class="token operator">!</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">var</span> e <span class="token operator">=</span> document<span class="token punctuation">,</span>
        t <span class="token operator">=</span> e<span class="token punctuation">.</span><span class="token function">createElement</span><span class="token punctuation">(</span><span class="token string">'script'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span><span class="token string">'noModule'</span> <span class="token keyword">in</span> t<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token string">'onbeforeload'</span> <span class="token keyword">in</span> t<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">var</span> n <span class="token operator">=</span> <span class="token operator">!</span><span class="token number">1</span><span class="token punctuation">;</span>
        e<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span>
            <span class="token string">'beforeload'</span><span class="token punctuation">,</span>
            <span class="token keyword">function</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>target <span class="token operator">===</span> t<span class="token punctuation">)</span> n <span class="token operator">=</span> <span class="token operator">!</span><span class="token number">0</span><span class="token punctuation">;</span>
                <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>e<span class="token punctuation">.</span>target<span class="token punctuation">.</span><span class="token function">hasAttribute</span><span class="token punctuation">(</span><span class="token string">'nomodule'</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token operator">!</span>n<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
                e<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span><span class="token punctuation">,</span>
            <span class="token operator">!</span><span class="token number">0</span>
        <span class="token punctuation">)</span><span class="token punctuation">,</span>
            <span class="token punctuation">(</span>t<span class="token punctuation">.</span>type <span class="token operator">=</span> <span class="token string">'module'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
            <span class="token punctuation">(</span>t<span class="token punctuation">.</span>src <span class="token operator">=</span> <span class="token string">'.'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
            e<span class="token punctuation">.</span>head<span class="token punctuation">.</span><span class="token function">appendChild</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span><span class="token punctuation">,</span>
            t<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
<span class="token operator">&lt;</span>script type<span class="token operator">=</span><span class="token string">"text/javascript"</span> src<span class="token operator">=</span><span class="token string">"main-legacy-f2fb0978e775b1b7d7e9.js"</span> nomodule<span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token operator">/</span>script<span class="token operator">&gt;</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">实现上，可以采用 Vue-CLI 的 Webpack 插件 <a href="https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-service/lib/webpack/ModernModePlugin.js">ModernModePlugin</a> 实现。在<a href="LINK">TODO 手写 plugin</a>的时候介绍过 html-webpack-plugin 这个插件，这个 webpack 的 HTML 插件，html-webpack-plugin 会在<code>compilation</code>对象上增加一些 Hook。</p>
</div><div class="cl-preview-section"><blockquote>
<p style="font-size: 20px; line-height: 38px;">Tips：Vue-CLI 的 ModernModePlugin 是基于 <a href="https://github.com/jantimon/html-webpack-plugin/tree/v3.2.0">html-webpack-plugin 3.2 版本</a>编写的，html-webpack-plugin 3.x 和最新的 html-webpack-plugin 4.x 使用 Hook 是不一样的，但是基本 Hook 的流程点是可以对上的，这部分包括 html-webpack-plugin 的 hook 使用方法在《<a href="LINK">TODO 手写 plugin</a>》小节有更加详细的介绍。</p>
</blockquote>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在 ModernModePlugin 中，我们使用了 html-webpack-plugin 的 v3 版本的两个 Hook：</p>
</div><div class="cl-preview-section"><ul>
<li style="font-size: 20px; line-height: 38px;"><code>compilation.hooks.htmlWebpackPluginAlterAssetTags</code></li>
<li style="font-size: 20px; line-height: 38px;"><code>compilation.hooks.htmlWebpackPluginAfterHtmlProcessing</code></li>
</ul>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">主要 Hook 是<code>htmlWebpackPluginAlterAssetTags</code>，在这个 Hook 中得到的<code>data</code>数据可以直接操作<code>data.head</code>和<code>data.body</code>这俩数组包含了 html-webpack-plugin 生成的 HTML 中在<code>&lt;head&gt;</code>和<code>&lt;body&gt;</code>的所有静态资源，包括 JavaScript 和 CSS 文件等。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">在 ModernModePlugin 中，需要区分开是 legacy 打包（非现代模式）还是 modern 打包：</p>
</div><div class="cl-preview-section"><ul>
<li style="font-size: 20px; line-height: 38px;">在 legacy 打包时：
<ul>
<li style="font-size: 20px; line-height: 38px;">这时候在<code>htmlWebpackPluginAlterAssetTags</code>中需要记录下来对应的 bundle 到 <code>legacy-assets-${htmlName}.json</code>；</li>
</ul>
</li>
<li style="font-size: 20px; line-height: 38px;">在 modern 打包时：
<ul>
<li style="font-size: 20px; line-height: 38px;">将 modern bundle 添加到 <code>html-webpack-plugin</code>的<code>data.head</code> 中添加<code>modulepreload</code>的<code>link</code>；</li>
<li style="font-size: 20px; line-height: 38px;">添加 Safari 10 的 polyfill 到<code>data.body</code>；</li>
<li style="font-size: 20px; line-height: 38px;">读取 legacy 打包时产生的<code>legacy-assets-${htmlName}.json</code>得到 legacy 的 bundle 内容，然后添加到<code>data.body</code>；</li>
</ul>
</li>
</ul>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">两者的关系连接是通过生成一个中间产物<code>legacy-assets-${htmlName}.json</code>来实现的！下面我们来详细解释下代码，首先是一个 Webpack 的插件的类结构是这样的：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">class</span> <span class="token class-name">ModernModePlugin</span> <span class="token punctuation">{</span>
    <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">{</span>targetDir<span class="token punctuation">,</span> isModernBuild<span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 接受Plugin 实例化时候传入的 options</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>targetDir <span class="token operator">=</span> targetDir<span class="token punctuation">;</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>isModernBuild <span class="token operator">=</span> isModernBuild<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token function">apply</span><span class="token punctuation">(</span>compiler<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 这是 Webpack Plugin 的核心apply函数</span>
        <span class="token comment">// 接收的是 comipler 对象</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>isModernBuild<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token comment">// 根据参数，进入不同的打包逻辑</span>
            <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">applyLegacy</span><span class="token punctuation">(</span>compiler<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
            <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">applyModern</span><span class="token punctuation">(</span>compiler<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token function">applyLegacy</span><span class="token punctuation">(</span>compiler<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
    <span class="token function">applyModern</span><span class="token punctuation">(</span>compiler<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">上面代码看到了，ModernModePlugin 是通过在使用的时候传入不同的 Options（isModernBuild） 而区分是 legacy 还是 modern 打包，如果是 legacy 打包则执行<code>this.applyLegacy</code>方法，如果是 modern 打包则执行<code>this.applyModern</code>方法。<code>options.targetDir</code>是用来存储<code>legacy-assets-${htmlName}.json</code>文件夹，这里直接使用 output 文件夹即可。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">下面再来看第一步，<code>applyLegacy</code>的实现：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> fs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'fs-extra'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">class</span> <span class="token class-name">ModernModePlugin</span> <span class="token punctuation">{</span>
    <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">{</span>targetDir<span class="token punctuation">,</span> isModernBuild<span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 接受Plugin 实例化时候传入的 options</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>targetDir <span class="token operator">=</span> targetDir<span class="token punctuation">;</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>isModernBuild <span class="token operator">=</span> isModernBuild<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token function">applyLegacy</span><span class="token punctuation">(</span>compiler<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> ID <span class="token operator">=</span> <span class="token string">'html-legacy-bundle'</span><span class="token punctuation">;</span>
        <span class="token comment">// 添加 compiler 对象的 Hook compilation，可以得到 compilation 对象</span>
        compiler<span class="token punctuation">.</span>hooks<span class="token punctuation">.</span>compilation<span class="token punctuation">.</span><span class="token function">tap</span><span class="token punctuation">(</span>ID<span class="token punctuation">,</span> compilation <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
            <span class="token comment">// 按照 v3 版本的 API 绑定htmlWebpackPluginAlterAssetTags Hook</span>
            compilation<span class="token punctuation">.</span>hooks<span class="token punctuation">.</span>htmlWebpackPluginAlterAssetTags<span class="token punctuation">.</span><span class="token function">tapAsync</span><span class="token punctuation">(</span>ID<span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>data<span class="token punctuation">,</span> cb<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
                <span class="token comment">// 使用 fs-extra 的 ensureDir 方法，如果不存在路径则创建，类似 mkdir -p 方法</span>
                <span class="token keyword">await</span> fs<span class="token punctuation">.</span><span class="token function">ensureDir</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>targetDir<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token comment">// data.plugin 是 html-webpack-plugin 插件的实例，options 可以得到对应的配置项</span>
                <span class="token comment">// 得到 html 的 name</span>
                <span class="token keyword">const</span> htmlName <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">basename</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>plugin<span class="token punctuation">.</span>options<span class="token punctuation">.</span>filename<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token comment">// 得到html 文件路径</span>
                <span class="token keyword">const</span> htmlPath <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">dirname</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>plugin<span class="token punctuation">.</span>options<span class="token punctuation">.</span>filename<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token comment">// 拼接临时文件的路径</span>
                <span class="token keyword">const</span> tempFilename <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>targetDir<span class="token punctuation">,</span> htmlPath<span class="token punctuation">,</span> <span class="token template-string"><span class="token string">`legacy-assets-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>htmlName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">.json`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token comment">// 调用 fs-extra 的 mkdirp 方法，先创建目录结构，相当于 mkdir -p</span>
                <span class="token keyword">await</span> fs<span class="token punctuation">.</span><span class="token function">mkdirp</span><span class="token punctuation">(</span>path<span class="token punctuation">.</span><span class="token function">dirname</span><span class="token punctuation">(</span>tempFilename<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token comment">// 将 data.body 内容格式化写到tempFilename文件</span>
                <span class="token keyword">await</span> fs<span class="token punctuation">.</span><span class="token function">writeFile</span><span class="token punctuation">(</span>tempFilename<span class="token punctuation">,</span> JSON<span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>body<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token function">cb</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">经过 <code>applyLegacy</code>处理后，生成的<code>legacy-assets-index.html.json</code>内容包含了<code>data.body</code>的内容，格式如下：</p>
</div><div class="cl-preview-section"><pre class=" language-json"><code class="prism  language-json"><span class="token punctuation">[</span>
    <span class="token punctuation">{</span>
        <span class="token string">"tagName"</span><span class="token punctuation">:</span> <span class="token string">"script"</span><span class="token punctuation">,</span>
        <span class="token string">"closeTag"</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
        <span class="token string">"attributes"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token string">"type"</span><span class="token punctuation">:</span> <span class="token string">"text/javascript"</span><span class="token punctuation">,</span> <span class="token string">"src"</span><span class="token punctuation">:</span> <span class="token string">"main-legacy-f2fb0978e775b1b7d7e9.js"</span><span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
    <span class="token comment">//...</span>
<span class="token punctuation">]</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><code>data.body</code>和<code>data.head</code>最终经过<a href="https://github.com/jantimon/html-webpack-plugin/blob/v3.2.0/index.js#L618">createHtmlTag</a>函数生成对应的 HTML String 片段，然后生成 HTML 页面！</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">legacy 打包首先打包，结束后就是 modern 的打包，这时候调用的是<code>applyModern</code>方法：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token keyword">const</span> fs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'fs-extra'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// 这个是 Safari 10 的 polyfill</span>
<span class="token comment">// 来自 https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc</span>
<span class="token keyword">const</span> safariFix <span class="token operator">=</span> <span class="token template-string"><span class="token string">`!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&amp;&amp;"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();`</span></span><span class="token punctuation">;</span>

<span class="token keyword">class</span> <span class="token class-name">ModernModePlugin</span> <span class="token punctuation">{</span>
    <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">{</span>targetDir<span class="token punctuation">,</span> isModernBuild<span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 接受Plugin 实例化时候传入的 options</span>

        <span class="token keyword">this</span><span class="token punctuation">.</span>targetDir <span class="token operator">=</span> targetDir<span class="token punctuation">;</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>isModernBuild <span class="token operator">=</span> isModernBuild<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token function">applyModern</span><span class="token punctuation">(</span>compiler<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> ID <span class="token operator">=</span> <span class="token string">'html-modern-bundle'</span><span class="token punctuation">;</span>
        <span class="token comment">// 添加 compiler 对象的 Hook compilation，可以得到 compilation 对象</span>
        compiler<span class="token punctuation">.</span>hooks<span class="token punctuation">.</span>compilation<span class="token punctuation">.</span><span class="token function">tap</span><span class="token punctuation">(</span>ID<span class="token punctuation">,</span> compilation <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
            <span class="token comment">// 按照 v3 版本的 API 绑定htmlWebpackPluginAlterAssetTags Hook</span>
            compilation<span class="token punctuation">.</span>hooks<span class="token punctuation">.</span>htmlWebpackPluginAlterAssetTags<span class="token punctuation">.</span><span class="token function">tapAsync</span><span class="token punctuation">(</span>ID<span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>data<span class="token punctuation">,</span> cb<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
                <span class="token comment">// 首先将 data.body 中的 js 添加上 &lt;script type="module"&gt; 用于 modern 浏览器识别使用</span>
                data<span class="token punctuation">.</span>body<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>tag <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
                    <span class="token keyword">if</span> <span class="token punctuation">(</span>tag<span class="token punctuation">.</span>tagName <span class="token operator">===</span> <span class="token string">'script'</span> <span class="token operator">&amp;&amp;</span> tag<span class="token punctuation">.</span>attributes<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                        tag<span class="token punctuation">.</span>attributes<span class="token punctuation">.</span>type <span class="token operator">=</span> <span class="token string">'module'</span><span class="token punctuation">;</span>
                    <span class="token punctuation">}</span>
                <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

                <span class="token comment">// 将 head 中的 link preload 资源更换成 modulepreload &lt;link rel="modulepreload"&gt;</span>
                data<span class="token punctuation">.</span>head<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>tag <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
                    <span class="token keyword">if</span> <span class="token punctuation">(</span>tag<span class="token punctuation">.</span>tagName <span class="token operator">===</span> <span class="token string">'link'</span> <span class="token operator">&amp;&amp;</span> tag<span class="token punctuation">.</span>attributes<span class="token punctuation">.</span>rel <span class="token operator">===</span> <span class="token string">'preload'</span> <span class="token operator">&amp;&amp;</span> tag<span class="token punctuation">.</span>attributes<span class="token punctuation">.</span><span class="token keyword">as</span> <span class="token operator">===</span> <span class="token string">'script'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                        tag<span class="token punctuation">.</span>attributes<span class="token punctuation">.</span>rel <span class="token operator">=</span> <span class="token string">'modulepreload'</span><span class="token punctuation">;</span>
                    <span class="token punctuation">}</span>
                <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

                <span class="token comment">// 得到 htmlName，实际是为了得到legacy 打包阶段生成的临时文件的路径</span>
                <span class="token keyword">const</span> htmlName <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">basename</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>plugin<span class="token punctuation">.</span>options<span class="token punctuation">.</span>filename<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token comment">// 得到 html 路径 ，实际是为了得到legacy 打包阶段生成的临时文件的路径</span>
                <span class="token keyword">const</span> htmlPath <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">dirname</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>plugin<span class="token punctuation">.</span>options<span class="token punctuation">.</span>filename<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token comment">// 拼接得到 legacy 打包阶段生成的临时文件</span>
                <span class="token keyword">const</span> tempFilename <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>targetDir<span class="token punctuation">,</span> htmlPath<span class="token punctuation">,</span> <span class="token template-string"><span class="token string">`legacy-assets-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>htmlName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">.json`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token comment">// 读取 legacy 打包阶段生成的临时文件</span>
                <span class="token keyword">const</span> legacyAssets <span class="token operator">=</span> JSON<span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span><span class="token keyword">await</span> fs<span class="token punctuation">.</span><span class="token function">readFile</span><span class="token punctuation">(</span>tempFilename<span class="token punctuation">,</span> <span class="token string">'utf-8'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>
                    a <span class="token operator">=&gt;</span> a<span class="token punctuation">.</span>tagName <span class="token operator">===</span> <span class="token string">'script'</span> <span class="token operator">&amp;&amp;</span> a<span class="token punctuation">.</span>attributes
                <span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token comment">// 给 legacyAssets 的script 标签加上 nomodule 属性，保证 modern 浏览器不能用</span>
                legacyAssets<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>a <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
                    a<span class="token punctuation">.</span>attributes<span class="token punctuation">.</span>nomodule <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">;</span>
                <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

                <span class="token comment">// 插入 Safari 10 nomodule polyfill</span>
                data<span class="token punctuation">.</span>body<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
                    tagName<span class="token punctuation">:</span> <span class="token string">'script'</span><span class="token punctuation">,</span>
                    closeTag<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
                    innerHTML<span class="token punctuation">:</span> safariFix
                <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token comment">// 将 legacyAssets添加到 modern 阶段生成的 data.body 上</span>
                data<span class="token punctuation">.</span>body<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token operator">...</span>legacyAssets<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token comment">// 删除临时文件</span>
                <span class="token keyword">await</span> fs<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>tempFilename<span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token function">cb</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment">// 租后是替换掉空的`nodemodule=''`属性</span>
            compilation<span class="token punctuation">.</span>hooks<span class="token punctuation">.</span>htmlWebpackPluginAfterHtmlProcessing<span class="token punctuation">.</span><span class="token function">tap</span><span class="token punctuation">(</span>ID<span class="token punctuation">,</span> data <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
                data<span class="token punctuation">.</span>html <span class="token operator">=</span> data<span class="token punctuation">.</span>html<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token regex">/\snomodule=""&gt;/g</span><span class="token punctuation">,</span> <span class="token string">' nomodule&gt;'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">到此，最后 html-webpack-plugin 就会生成对应的 HTML 代码了！</p>
</div><div class="cl-preview-section"><h2 id="测试" style="font-size: 30px;">测试</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">最后在来测试下我们的 Modern 模式打包全流程，继续修改<code>build.js</code>内容，让它先打包 legacy 包，成功之后在打包 modern 包，具体的代码如下：</p>
</div><div class="cl-preview-section"><pre class=" language-js"><code class="prism  language-js"><span class="token comment">// 添加各自的 ModernModePlugin，传入不同的isModernBuild</span>
webpackConfig<span class="token punctuation">.</span>plugins<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ModernModePlugin</span><span class="token punctuation">(</span><span class="token punctuation">{</span>targetDir<span class="token punctuation">:</span> __dirname <span class="token operator">+</span> <span class="token string">'/dist'</span><span class="token punctuation">,</span> isModernBuild<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
modernConfig<span class="token punctuation">.</span>plugins<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ModernModePlugin</span><span class="token punctuation">(</span><span class="token punctuation">{</span>targetDir<span class="token punctuation">:</span> __dirname <span class="token operator">+</span> <span class="token string">'/dist'</span><span class="token punctuation">,</span> isModernBuild<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 首先打包 legacy</span>
<span class="token comment">// 成功后打包 modern</span>
<span class="token function">webpackPromise</span><span class="token punctuation">(</span>webpackConfig<span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>stats <span class="token operator">=&gt;</span> Promise<span class="token punctuation">.</span><span class="token function">all</span><span class="token punctuation">(</span><span class="token punctuation">[</span>Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span>stats<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">webpackPromise</span><span class="token punctuation">(</span>modernConfig<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">[</span>legacyStats<span class="token punctuation">,</span> modernStats<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token comment">// 输出老版本打包</span>
        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>legacyStats<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">{</span>chunks<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> modules<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> colors<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token comment">// 输出 modern 版本打包</span>
        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>modernStats<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">{</span>chunks<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> modules<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> colors<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token punctuation">.</span><span class="token keyword">catch</span><span class="token punctuation">(</span>e <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
</div><div class="cl-preview-section"><h2 id="总结" style="font-size: 30px;">总结</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">本小节的实战内容主要讲解了怎么来实现 Vue-CLI 的 Modern Mode 模式打包，首先我们认识了如何利用 Babel 的配置打出现代浏览器执行的代码，然后我们讲到可以通过使用<code>&lt;script type="module"&gt;</code>的方式来让现代浏览器执行 Modern 模式打包出来的文件，然后利用<code>&lt;script nomodule&gt;</code>来让现代浏览器忽略 legacy 的代码，这时候需要注意到 Safari 10 中不支持 script 标签的<code>nomodule</code>属性，需要添加 polyfill 代码。Legacy 和 Modern 代码打出来之后，需要做的是利用 html-webpack-plugin 的插件，将两次打包的 bundle 文件合并到一个 HTML 页面中，modern 的代码使用<code>&lt;script type="module"&gt;</code>加载，legacy 的代码使用<code>&lt;script nomodule&gt;</code>方式加载，同时给 Safari 10 添加 polyfill。在插件实现上，我们直接使用了 ModernModePlugin，在 legacy 打包时，将<code>data.body</code>内容生成 JSON 存储起来，在 Modern 打包的时候，读取 JSON 内容，合并两次打包的 Bundle 资源，生成 HTML。本小节的实战内容可以直接在项目中实践，如果你现在的项目不支持 Modern 模式打包，你可以尝试使用本小节的代码给项目添加 Modern 模式打包。</p>
</div><div class="cl-preview-section"><blockquote>
<p style="font-size: 20px; line-height: 38px;">戳此访问<a href="https://github.com/ksky521/webpack-tutorial/packages/charpter-05/06-modern">本小节源码：webpack-tutorial/packages/charpter-05/06-modern</a></p>
</blockquote>
</div><div class="cl-preview-section"><blockquote>
<p style="font-size: 20px; line-height: 38px;">本小节 Webpack 相关面试题：</p>
<ol>
<li style="font-size: 20px; line-height: 38px;">Vue-CLI 3 中的 modern mode 是怎么实现的？</li>
<li style="font-size: 20px; line-height: 38px;">如何让自己的项目在浏览器中直接执行 ES2015+ 代码？</li>
<li style="font-size: 20px; line-height: 38px;">你能够说出 Vue-CLI 3 一个印象深刻的功能吗？</li>
</ol>
</blockquote>
</div></div>
            </div>
                            <!-- 买过的阅读 -->
                <div class="art-next-prev clearfix">
                                                                        <!-- 已买且开放 或者可以试读 -->
                            <a href="/read/29/article/291">
                                                    <div class="prev l clearfix">
                                <div class="icon l">
                                    <i class="imv2-arrow3_l"></i>
                                </div>
                                <p>
                                    实战：使用 Stats 数据结构生成 Webpack 构建报告
                                </p>
                            </div>
                        </a>
                                                                                            <!-- 已买且开放 或者可以试读 -->
                            <a href="/read/29/article/293">
                                                    <div class="next r clearfix">
                                <p>
                                    Webpack 5.0
                                </p>
                                <div class="icon r">
                                    <i class="imv2-arrow3_r"></i>
                                </div>

                            </div>
                        </a>
                                    </div>
                    </div>
        <div class="comments-con js-comments-con" id="coments_con">     <div class="number">精选留言 <span class="js-number">0</span></div>     <div class="comments">         <div class="input-fake js-showcommentModal">             欢迎在这里发表留言，作者筛选后可公开显示         </div>                      <div class="noData">                 <p>                     <i class="imv2-error_c"></i>                 </p>                 <p>目前暂无任何讨论</p>             </div>              </div>  </div>



    </div>
    
    
    

</div>
 
<!-- 专栏介绍页专栏评价 -->

<!-- 专栏介绍页底部三条评价 -->

<!-- 专栏阅读页弹层目录和介绍页页面目录 -->

<!-- 专栏阅读页发布回复 -->

<!-- 专栏阅读页发布评论 -->

<!-- 专栏阅读页底部评论 -->

<!-- 专栏阅读 单个 评论 -->

<!-- 新增回复和展开三条以外回复 -->

<!-- 立即订阅的弹窗 -->












</div></body></html>